package cz.cuni.mff.ufal.volk.services;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import cz.cuni.mff.ufal.volk.Speech;
import cz.cuni.mff.ufal.volk.utils.Utils;

/**
 * Speech converter that uses Sound eXchange program for converting sound.
 *
 * @author Bartłomiej Etenkowski
 */
public class SoxSpeechConverter implements SpeechConverter {

  //[start] logger
  private static final org.apache.log4j.Logger log = org.apache.log4j.Logger
      .getLogger(SoxSpeechConverter.class);
  //[end]

  public SoxSpeechConverter(String soxPath) {
  	this.soxPath = soxPath;
    for (String format : supportedFormats0) {
      supportedFormats.add(format);
    }
  }

  private final String[] supportedFormats0 = new String[] { "wav/signed-integer",
      "wav/unsigned-integer", "wav/oki-adpcm" };
  private final List<String> supportedFormats = new ArrayList<String>(supportedFormats0.length);

  /**
   * Returns the list of all supported formats.
   * @return the list of all supported formats
   */
  @Override
  public List<String> sourceFormats() {
    return Collections.unmodifiableList(supportedFormats);
  }

  /**
   * Returns the list of all supported formats.
   * @return the list of all supported formats
   */
  @Override
  public List<String> sourceFormats(String targetFormat) {
    return Collections.unmodifiableList(supportedFormats);
  }

  /**
   * Returns the list of all supported formats.
   * @return the list of all supported formats
   */
  @Override
  public List<String> outputFormats() {
    return Collections.unmodifiableList(supportedFormats);
  }

  /**
   * Returns the list of all supported formats.
   * @return the list of all supported formats
   */
  @Override
  public List<String> outputFormats(String sourceFormat) {
    return Collections.unmodifiableList(supportedFormats);
  }

  private final String soxPath;

  /**
   * Checks whether the particular format is supported by the converter.
   * @param  format the format to check
   * @return whether the format is supported by the converter
   * @throws NullPointerException if the {@code format} parameter is {@code null}
   */
  public boolean formatSupported(String format) {
    format.getClass();
    return supportedFormats.contains(format);
  }

  /**
   * Converts speech in a source format into the desired target format.
   *
   * @param  input the input to convert
   * @param  targetFormat the target format
   * @return the speech converted into the requested format
   *
   * @throws NullPointerException if any of the parameters is {@code null}
   * @throws IllegalArgumentException if the {@code targetFormat}, source format or the pair
   *         of formats is not supported by the converter
   */
  @Override
  public Speech convert(Speech input, String targetFormat) throws IllegalArgumentException {
    try {
      File inputTemp = File.createTempFile("SOX", null);
      inputTemp.deleteOnExit();
      File outputTemp = File.createTempFile("SOX", null);
      outputTemp.deleteOnExit();
      try {
        return convert(input, targetFormat, inputTemp, outputTemp);
      } finally {
        inputTemp.delete();
        outputTemp.delete();
      }
    } catch (IOException e) {
      // TODO Auto-generated catch block
      log.error("An I/O exception occurred", e);
      throw new RuntimeException(e);
    } catch (InterruptedException e) {
      log.error("An InterruptedException occurred", e);
      throw new RuntimeException(e);
    }
  }

  private Speech convert(Speech input, String targetFormat, File inputTemp,
      File outputTemp) throws IOException, InterruptedException {
    Runtime runtime = Runtime.getRuntime();
    if (formatSupported(targetFormat)) {
      Utils.writeAllBytes(inputTemp, input.getData());
      String[] cmdarray = createCmdArray(targetFormat, inputTemp, outputTemp);
      log.info("cmdarray=" + cmdarray.toString());
      Process process = runtime.exec(cmdarray);
      int exitValue = process.waitFor();
      if (exitValue == 0) {
        byte[] data = Utils.readAllBytes(outputTemp);
        return new Speech(input.getLanguage(), targetFormat, data);
      } else {
        throw new RuntimeException(String.format(
            "Cannot convert the input data, the SOX exit value is %d", exitValue));
      }
    } else {
      throw new IllegalArgumentException("The target format is not supported");
    }
  }

  private String[] createCmdArray(String targetFormat, File inputTemp, File outputTemp) {
    List<String> cmdlist = new ArrayList<String>();
    cmdlist.add(soxPath);
    cmdlist.add(inputTemp.getAbsolutePath());
    String[] splfmt = targetFormat.split("/");
    cmdlist.add("-t");
    cmdlist.add(splfmt[0]);
    if (splfmt.length > 1) {
      cmdlist.add("-e");
      cmdlist.add(splfmt[1]);
    }
    cmdlist.add(outputTemp.getAbsolutePath());
    String[] cmdarray = cmdlist.toArray(new String[0]);
    return cmdarray;
  }
}
